package com.autonavi.yunda.yunji.core.engine;

import com.autonavi.yunda.yunji.common.exception.AmapException;
import com.autonavi.yunda.yunji.common.exception.AmapExceptionCode;
import com.autonavi.yunda.yunji.core.config.ApplicationEnvConfig;
import com.autonavi.yunda.yunji.core.engine.argresolver.ParamResolver;
import com.autonavi.yunda.yunji.core.engine.argresolver.RequestArgsResolverComposite;
import com.autonavi.yunda.yunji.core.engine.context.EngineContext;
import com.autonavi.yunda.yunji.core.engine.exception.EngineException;
import com.autonavi.yunda.yunji.core.engine.script.InterfaceScriptClass;
import com.autonavi.yunda.yunji.core.enums.EngineType;
import com.autonavi.yunda.yunji.core.enums.Env;
import com.autonavi.yunda.yunji.core.utils.ExportUtil;
import com.autonavi.yunda.yunji.core.utils.ProtobufUtil;
import com.autonavi.yunda.yunji.core.vo.DebugResult;
import com.autonavi.yunda.yunji.core.vo.EngineData;
import com.autonavi.yunda.yunji.core.vo.HttpRequest;
import com.autonavi.yunda.yunji.core.vo.ProjectExtraData;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.groovy.util.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

import static com.autonavi.yunda.yunji.core.config.EngineConstant.*;

/**
 * @author cizhong.wcs
 * @date 2022/6/24 16:13
 */
@Slf4j
@Component
public class Engine {

    private static final Logger engineLogger = LoggerFactory.getLogger("logger.engine.exec.log");
    private static final Logger TRACE_LOGGER = LoggerFactory.getLogger("TRACE_LOG");
    @Resource
    private ParallelEngineWorker parallelEngineWorker;
    @Resource
    private SerialEngineWorker serialEngineWorker;
    @Resource
    private ApplicationEnvConfig applicationEnvConfig;

    @Autowired(required = false)
    private HttpServletResponse httpServletResponse;

    /**
     * http 请求执行接口
     */
    public Object exeApi(InterfaceScriptClass scriptClass, HttpRequest request, HttpServletResponse response) {
        EngineContext engineContext = new EngineContext(scriptClass);
        ProjectExtraData projectExtraData = scriptClass.projectExtraData;
        Object result;
        Object responseCode = convertCode(projectExtraData.defaultSuccessCode, projectExtraData.codeType);
        try {
            Object output = execEngineWorker(engineContext, request);
            if (specialProtocolReturn(request, response, output)) {
                result = null;
            } else {
                // 返回字段中有 _realOutput 以 _realOutput 字段的value作为输出
                Map<String, Object> outputMap = (Map<String, Object>) output;
                if (outputMap.containsKey(ENGINE_REAL_OUTPUT)) {
                    output = outputMap.get(ENGINE_REAL_OUTPUT);
                }
                result = engineContext.invokeResponseSuccess(responseCode, output);
                Map<String, Object> responseHeaders = (Map<String, Object>) engineContext.invokeResponseHeaders();
                // 设置响应头
                for (Map.Entry<String, Object> entry : responseHeaders.entrySet()) {
                    response.addHeader(entry.getKey(), entry.getValue() + "");
                }
            }
        } catch (EngineException e) {
            responseCode = convertCode(e.getCode(), projectExtraData.codeType);
            result = engineContext.invokeResponseFail(responseCode, e.getMessage());
        } catch (AmapException e) {
            engineLogger.warn("Engine exe warn", e);
            responseCode = convertCode(e.getExceptionCode().getCode(), projectExtraData.codeType);
            result = engineContext.invokeResponseFail(responseCode, e.getExceptionCode().getText());
        } catch (Exception e) {
            log.error("Engine exe error", e);
            responseCode = convertCode(projectExtraData.defaultErrorCode, projectExtraData.codeType);
            result = engineContext.invokeResponseFail(responseCode, "系统未定义异常");
        } finally {
            logTrace(engineContext, responseCode.toString());
        }
        return result;
    }

    /**
     * debug 执行接口全链路
     */
    public DebugResult debugApi(InterfaceScriptClass scriptClass, HttpRequest request) {
        EngineContext engineContext = new EngineContext(scriptClass);
        engineContext.debug = true;
        ProjectExtraData projectExtraData = scriptClass.projectExtraData;
        Object output = null;
        String errorMessage = null;
        Object response;
        String executeStatus = DEBUG_EXEC_FAIL;
        Object responseCode = convertCode(projectExtraData.defaultSuccessCode, projectExtraData.codeType);
        Map<String, String> responseHeaders = new HashMap<>();
        boolean skipReturnDebugResult = false;
        try {
            output = execEngineWorker(engineContext, request);
            // 返回字段中有 _realOutput 以 _realOutput 字段的value作为输出
            Map<String, Object> outputMap = (Map<String, Object>) output;
            if (outputMap.containsKey(ENGINE_REAL_OUTPUT)) {
                output = outputMap.get(ENGINE_REAL_OUTPUT);
            }
            if (outputMap.containsKey(CUSTOM_RESPONSE)) {
                skipReturnDebugResult = true;
            }
            response = engineContext.invokeResponseSuccess(responseCode, output);
            Map<String, Object> headers = (Map<String, Object>) engineContext.invokeResponseHeaders();
            // 设置响应头
            for (Map.Entry<String, Object> entry : headers.entrySet()) {
                responseHeaders.put(entry.getKey(), entry.getValue() + "");
            }
            executeStatus = DEBUG_EXEC_SUCCESS;
        } catch (EngineException e) {
            //返回 EngineException 算作符合逾期的异常. debug执行状态置为成功.
            executeStatus = DEBUG_EXEC_SUCCESS;
            responseCode = convertCode(e.getCode(), projectExtraData.codeType);
            response = engineContext.invokeResponseFail(responseCode, e.getMessage());
            errorMessage = e.getMessage();
        } catch (AmapException e) {
            engineLogger.warn("Engine debug warn", e);
            responseCode = convertCode(e.getExceptionCode().getCode(), projectExtraData.codeType);
            response = engineContext.invokeResponseFail(responseCode, e.getExceptionCode().getText());
            errorMessage = e.getErrorMessage();
        } catch (Exception e) {
            log.error("Engine debug error", e);
            responseCode = convertCode(projectExtraData.defaultErrorCode, projectExtraData.codeType);
            response = engineContext.invokeResponseFail(responseCode, "系统未定义异常");
            if (e instanceof NullPointerException) {
                errorMessage = "空指针异常";
            } else {
                errorMessage = e.getMessage();
            }
        } finally {

            logTrace(engineContext, responseCode.toString());
        }
        DebugResult debugResult = new DebugResult(executeStatus, response, output, engineContext, errorMessage, responseHeaders);
        debugResult.setSkipReturnDebugResult(skipReturnDebugResult);
        return debugResult;
    }

    public Object execEngineWorker(EngineContext engineContext, HttpRequest request) {
        EngineData engineData = engineContext.scriptClass.engineData;
        Map<String, Object> inputParams = resolveInputParams(engineData, request);
        return execEngineWorker(engineContext, inputParams);
    }


    public Object execEngineWorker(EngineContext engineContext, Map<String, Object> inputParams) {
        if (engineContext.engineType.equals(EngineType.SERIAL)) {
            serialEngineWorker.doExe(engineContext, inputParams);
        } else {
            parallelEngineWorker.doExe(engineContext, inputParams);
        }
        checkContextAndThrow(engineContext);
        return engineContext.invoke(OUTPUT_FUN_NAME);
    }

    /**
     * 特殊协议 提前返回
     */
    private boolean specialProtocolReturn(HttpRequest request, HttpServletResponse response, Object output) {
        if (output == null) {
            return false;
        }
        Map<String, Object> outputMap = (Map<String, Object>) output;
        if (outputMap.containsKey(CUSTOM_RESPONSE)) {
            return true;
        }
        if (request.getParameterMap().containsKey(EXPORT_RESULT_FLAG)) {
            // 返回字段中有 _realOutput 以 _realOutput 字段的value作为输出

            if (outputMap.containsKey(ENGINE_REAL_OUTPUT)) {
                output = outputMap.get(ENGINE_REAL_OUTPUT);
            }
            String suffix = request.getParameterMap().get(EXPORT_RESULT_FLAG)[0];
            output = output == null ? "" : output;
            ExportUtil.write(response, output, suffix);
            return true;
        }
        //output application/x-protobuf
        if (outputMap.containsKey(PROTOBUF)) {
            final Object data = outputMap.get(PROTOBUF);
            if (data instanceof byte[]) {
                ProtobufUtil.write(response, (byte[]) data);
            } else if (data instanceof String) {
                ProtobufUtil.write(response, ((String) data).getBytes());
            }
            return true;
        }

        return false;
    }

    public static void checkContextAndThrow(EngineContext engineContext) {
        if (engineContext.exceptionNodes().isEmpty()) {
            return;
        }
        Pair<String, Throwable> nameException = engineContext.fetchFirstException();
        if (nameException.getRight() instanceof EngineException) {
            throw (EngineException) nameException.getRight();
        } else if (nameException.getRight() instanceof AmapException) {
            throw (AmapException) nameException.getRight();
        } else {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_ERROR, nameException.getLeft() + "节点执行异常");
        }
    }

    private Object convertCode(Object code, String type) {
        try {
            return ParamResolver.getParamValueByType(code, type);
        } catch (Exception e) {
            log.error("convertCode error ", e);
        }
        return code;
    }

    /**
     * 记录接口调用日志
     */
    private void logTrace(EngineContext engineContext, String responseCode) {
        try {
            Map<String, Object> traceLogInfo = (Map<String, Object>) engineContext.invokeTraceLog();
            String uri = engineContext.scriptClass.uri;
            Long interfaceId = engineContext.scriptClass.interfaceId;
            String projectId = engineContext.scriptClass.projectId;
            Object userInfo = traceLogInfo.getOrDefault("userInfo", "-");
            Object chargeCnt = traceLogInfo.getOrDefault("chargeCnt", 1);
            Object customInfo = traceLogInfo.getOrDefault("customInfo", "-");
            Env env = applicationEnvConfig.getEnv();
            String traceLog = String.format(
                    "%s###%s###%s###%s###%s###%s###%s###%s###%s",
                    uri, interfaceId, projectId, chargeCnt, engineContext.executeTime, responseCode, env.getCode(), userInfo, customInfo);
            engineContext.traceLog = traceLog;
            TRACE_LOGGER.info(traceLog);
        } catch (Exception e) {
            log.error("logTrace error ", e);
        }
    }

    public Map<String, Object> resolveInputParams(EngineData engineData, HttpRequest request) {
        Map<String, Object> inputParams = new HashMap<>(8);
        RequestArgsResolverComposite resolver = new RequestArgsResolverComposite();
        try {
            engineData.fetchStartNode().inputParams.forEach(param -> inputParams.put(param.getParamNameInScript(), resolver.resolve(request, param)));
            Map<String, Object> requestData = new HashMap<>(inputParams);
            inputParams.put(CONTEXT_PARAM_NAME, Maps.of("requestData", requestData,
                    "httpResponse", httpServletResponse,
                    "httpRequest", request));
        } catch (Exception e) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_WARN, "参数解析异常,请检查参数是否符合规范", e);
        }
        return inputParams;
    }
}
